package com.d4a5.mapping.core 
{
	public class MathStringParser
	{
		protected static var _operators:Object;
		protected static var _functions:Object;
		protected static var _constants:Object;
		protected static var _numbers:Object;
		
		public static function parse( equation:String , lut:Object = null ) : Number {
			if( MathStringParser._operators == null ){
				
				MathStringParser._operators = new Object();
				MathStringParser._operators["*"] = { precedence : 1 , method : function multiply( a:Number , b:Number ):Number { return a * b; } };
				MathStringParser._operators["+"] = { precedence : 2 , method : function add( a:Number , b:Number ):Number { return a + b; } };
				MathStringParser._operators["-"] = { precedence : 2 , method : function subtract( a:Number , b:Number ):Number { return a - b; } };
				MathStringParser._operators["/"] = { precedence : 1 , method : function divide( a:Number , b:Number ):Number { return a / b; } } ;
				MathStringParser._operators["^"] = { precedence : 0 , method : Math.pow };
				
				MathStringParser._functions = new Object();
				MathStringParser._functions["abs"] = Math.abs;
				MathStringParser._functions["acos"] = Math.acos;
				MathStringParser._functions["asin"] = Math.asin;
				MathStringParser._functions["atan"] = Math.atan;
				MathStringParser._functions["ceil"] = Math.ceil;
				MathStringParser._functions["cos"] = Math.cos;
				MathStringParser._functions["exp"] = Math.exp;
				MathStringParser._functions["floor"] = Math.floor;
				MathStringParser._functions["ln"] = Math.log;
				MathStringParser._functions["pow"] = Math.pow;
				MathStringParser._functions["round"] = Math.round;
				MathStringParser._functions["sin"] = Math.sin;
				MathStringParser._functions["sqrt"] = Math.sqrt;
				MathStringParser._functions["tan"] = Math.tan;
				
				MathStringParser._constants = new Object();
				MathStringParser._constants["PI"] = Math.PI;
				MathStringParser._constants["E"] = Math.E;
				MathStringParser._constants["LN10"] = Math.LN10;
				MathStringParser._constants["LN2"] = Math.LN2;
				MathStringParser._constants["LOG10E"] = Math.LOG10E;
				MathStringParser._constants["LOG2E"] = Math.LOG2E;
				MathStringParser._constants["SQRT1_2"] = Math.SQRT1_2;
				MathStringParser._constants["SQRT2"] = Math.SQRT2;
			}
			
			
			return MathStringParser.recurseThroughEquation( new ParseObject( equation , lut ) ).value;
		}
		
		protected static function recurseThroughEquation( obj:ParseObject ) : ParseObject {
			
			var current:String = "";
			var values:Array = new Array();
			var operations:Array = [ [] , [] , [] ];
			var needToWalkThruOrderOfPrecedence:Boolean = false;
			
			for( ; obj.currentPosition < obj.numChars; obj.currentPosition++ ){
				var char:String = obj.equation.charAt( obj.currentPosition );
				
				if( char == "(" ){
					obj.currentPosition++;
					//go find out the value in the parens
					obj = MathStringParser.recurseThroughEquation( obj );
					
					//now use that value
					if( MathStringParser._functions[ current ] ){//predefined math function
						current = MathStringParser._functions[ current ]( obj.value ).toPrecision(20);
					}
					else{//looks like 39(54) so multiply 39*54
						needToWalkThruOrderOfPrecedence = true;
						
						values.push( MathStringParser._constants[ current ] 
							? MathStringParser._constants[ current ] 
							: ( obj.lut[ current ] ) 
								? Number(obj.lut[ current ]) 
								: Number( current ) );
						
						
						operations[ MathStringParser._operators["*"].precedence ].push( {a:values.length-1,b:values.length,operator:"*"} );
						current = "";
						
						values.push( obj.value );
					}
					
				}
				else if( char == ")" ){
					break;
				}
				else if( MathStringParser._operators[char] ){ // char is +,-,*,/,^
					needToWalkThruOrderOfPrecedence = true;
					
					values.push( MathStringParser._constants[ current ] 
						? MathStringParser._constants[ current ] 
						: ( obj.lut[ current ] ) 
							? Number(obj.lut[ current ]) 
							: Number( current ) );
					
					operations[ MathStringParser._operators[char].precedence ].push( {a:values.length-1,b:values.length,operator:char} );
					current = "";
				}
				else{
					current += char; 	
				}
			}
			
			//reached the end of the string so we need to add the last value on to the list
			values.push( MathStringParser._constants[ current ] 
				? MathStringParser._constants[ current ] 
				: ( obj.lut[ current ] ) 
					? Number(obj.lut[ current ]) 
					: Number( current ) );
				
			
			
			
			if( needToWalkThruOrderOfPrecedence ){
				for( var orderOfPrecedence:int = 0; orderOfPrecedence < 3; orderOfPrecedence++ ){
					var functions:Array = operations[ orderOfPrecedence ] as Array;
					var numFunctions:int = functions.length;
					
					for( var f:int = 0; f < numFunctions ; f++ ){
						var func:Object = functions[f];
						var currentFunction:Function = MathStringParser._operators[ func.operator ].method as Function;
						obj.value = values[func.a] = values[func.b] = currentFunction( values[func.a], values[func.b] );
					}
				}
			}
			else{
				obj.value = values[0] as Number;
			}
			
			return obj;
		}
		
		
		
	}
}

class ParseObject
{
	public var currentPosition:int;
	public var numChars:int;
	public var equation:String;
	public var lut:Object;
	public var value:Number;
	
	public function ParseObject( equation:String , lut:Object = null ){
		this.equation = equation;
		this.numChars = this.equation.length;
		this.currentPosition = 0;
		this.lut = ( lut ) ? lut : new Object();
		this.value = Number.NaN;
	}
}